﻿using System;
using System.Threading.Tasks;
using System.Transactions;

namespace ServerUtils
{
    public class MRInvokerWithTransaction
    {
        private TransactionOptions transactionOptions;
        private TransactionScopeOption transactionScopeOption;
        private MRInvoker mRInvoker;

        public TimeSpan Timeout
        {
            get
            {
                return transactionOptions.Timeout;
            }

            set
            {
                this.transactionOptions = new TransactionOptions
                {
                    IsolationLevel = this.transactionOptions.IsolationLevel,
                    Timeout = value
                };
            }
        }

        public event EventHandler<InvokerErrorEventArgs> Error
        {
            add
            {
                this.mRInvoker.Error += value;
            }

            remove
            {
                this.mRInvoker.Error -= value;
            }
        }

        public MRInvokerWithTransaction(
            int retryInterval, 
            int maxRetry, 
            IsolationLevel isolationLevel = IsolationLevel.ReadCommitted, 
            TransactionScopeOption transactionScopeOption = TransactionScopeOption.Required)
        {
            this.mRInvoker = new MRInvoker(retryInterval, maxRetry);
            this.transactionOptions = new TransactionOptions { IsolationLevel = isolationLevel };
            this.transactionScopeOption = transactionScopeOption;
        }

        public void Invoke(Action action)
        {
            Action a = () =>
            {
                using (var scope = this.CreateTransactionScope())
                {
                    action.Invoke();
                    scope.Complete();
                }
            };

            this.mRInvoker.Invoke(a);
        }

        public TResult Invoke<TResult>(Func<ITransactionalOperation<TResult>> func)
        {
            Func<TResult> f = () =>
            {
                using (var scope = this.CreateTransactionScope())
                {
                    var result = func.Invoke();
                    if (result.CanCommit)
                    {
                        scope.Complete();
                    }

                    return result.Result;
                }
            };

            return this.mRInvoker.Invoke<TResult>(f);
        }

        public async Task InvokeAsync(Func<Task> asyncAction)
        {
            Func<Task> f = async () =>
            {
                using (var scope = this.CreateTransactionScopeForAsyncOp())
                {
                    await asyncAction.Invoke();
                    scope.Complete();
                }
            };

            await this.mRInvoker.InvokeAsync(f);
        }

        public async Task<TResult> InvokeAsync<TResult>(Func<Task<ITransactionalOperation<TResult>>> asyncFunc)
        {
            Func<Task<TResult>> f = async () =>
            {
                using (var scope = this.CreateTransactionScopeForAsyncOp())
                {
                    var result = await asyncFunc.Invoke();
                    if (result.CanCommit)
                    {
                        scope.Complete();
                    }

                    return result.Result;
                }
            };
            
            return await this.mRInvoker.InvokeAsync<TResult>(f);
        }

        private TransactionScope CreateTransactionScope()
        {
            return new TransactionScope(this.transactionScopeOption, this.transactionOptions);
        }

        private TransactionScope CreateTransactionScopeForAsyncOp()
        {
            return new TransactionScope(this.transactionScopeOption, this.transactionOptions, TransactionScopeAsyncFlowOption.Enabled);
        }
    }
}
